/* * Contributions to FindBugs * Copyright (C) 2006, Institut for Software * An Institut of the University of Applied Sciences Rapperswil * * Author: Thierry Wyss, Marco Busarello * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package edu.umd.cs.findbugs.plugin.eclipse.quickfix; import static edu.umd.cs.findbugs.plugin.eclipse.quickfix.util.ASTUtil.addImports; import static edu.umd.cs.findbugs.plugin.eclipse.quickfix.util.ASTUtil.getASTNode; import static edu.umd.cs.findbugs.plugin.eclipse.quickfix.util.ConditionCheck.checkForNull; import static org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.PUBLIC_KEYWORD; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import javax.annotation.CheckForNull; import org.eclipse.core.runtime.Assert; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.BodyDeclaration; import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.ImportDeclaration; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.MethodInvocation; import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.ParameterizedType; import org.eclipse.jdt.core.dom.ReturnStatement; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SimpleType; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.dom.VariableDeclarationStatement; import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; import org.eclipse.jdt.core.dom.rewrite.ListRewrite; import edu.umd.cs.findbugs.BugInstance; import edu.umd.cs.findbugs.plugin.eclipse.quickfix.exception.BugResolutionException; import edu.umd.cs.findbugs.plugin.eclipse.quickfix.util.ImportDeclarationComparator; /** * A <CODE>ClassLoader</CODE>, which requires a security manager, might be * invoked by code that does not have security permissions.In this case the * <CODE>ClassLoader</CODE> creation needs to occur inside a * <CODE>doPrivileged()</CODE>-Block. The class * <CODE>CreateDoPrivilegedBlockResolution</CODE> creates a new * <CODE>doPrivileged()</CODE>-Block around the <CODE>ClassLoader</CODE> * creation. * * @see <a * href="http://findbugs.sourceforge.net/bugDescriptions.html#DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED">DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED</a> * @author <a href="mailto:twyss@hsr.ch">Thierry Wyss</a> * @author <a href="mailto:mbusarel@hsr.ch">Marco Busarello</a> * @version 1.0 */ public class CreateDoPrivilegedBlockResolution extends BugResolution { private static final String DO_PRIVILEGED_METHOD_NAME = "doPrivileged"; //$NON-NLS-1$ private boolean updateImports = true; private boolean staticImport = false; private Comparator<ImportDeclaration> importComparator = new ImportDeclarationComparator<ImportDeclaration>(); public CreateDoPrivilegedBlockResolution() { super(); } public CreateDoPrivilegedBlockResolution(boolean updateImports) { this(); setUpdateImports(updateImports); } public CreateDoPrivilegedBlockResolution(boolean updateImports, boolean staticImport) { this(updateImports); setStaticImport(staticImport); } /** * Returns <CODE>true</CODE> if the imports were updated, otherwise * <CODE>false</CODE>. * * @return <CODE>true</CODE> or <CODE>false</CODE>. Default is * <CODE>true</CODE>. */ public boolean isUpdateImports() { return updateImports; } /** * Enables or disables the update on the imports. * * @param updateImports * the flag. */ public void setUpdateImports(boolean updateImports) { this.updateImports = updateImports; } /** * Returns <CODE>true</CODE> if the <CODE>doPrivileged()</CODE>-invocation * is imported statically. This feature should only be used under * source-level 1.5 or higher. * * @return <CODE>true</CODE> or </CODE>false</CODE>. Default is * <CODE>false</CODE>. */ public boolean isStaticImport() { return staticImport; } /** * Enables or disables static import for the <CODE>doPrivileged()</CODE> * -invocation. This feature should only be used under source-level 1.5 or * higher. * * @param staticImport * the flag. */ public void setStaticImport(boolean staticImport) { this.staticImport = staticImport; } public Comparator<ImportDeclaration> getImportComparator() { return importComparator; } public void setImportComparator(Comparator<ImportDeclaration> importComparator) { checkForNull(importComparator, "import comparator"); this.importComparator = importComparator; } @Override protected boolean resolveBindings() { return true; } @Override protected void repairBug(ASTRewrite rewrite, CompilationUnit workingUnit, BugInstance bug) throws BugResolutionException { Assert.isNotNull(rewrite); Assert.isNotNull(workingUnit); Assert.isNotNull(bug); ClassInstanceCreation classLoaderCreation = findClassLoaderCreation(getASTNode(workingUnit, bug.getPrimarySourceLineAnnotation())); if (classLoaderCreation == null) { throw new BugResolutionException("No matching class loader creation found at the specified source line."); } updateVariableReferences(rewrite, classLoaderCreation); rewrite.replace(classLoaderCreation, createDoPrivilegedInvocation(rewrite, classLoaderCreation), null); updateImportDeclarations(rewrite, workingUnit); } protected void updateVariableReferences(ASTRewrite rewrite, ClassInstanceCreation classLoaderCreation) { Assert.isNotNull(rewrite); Assert.isNotNull(classLoaderCreation); MethodDeclaration method = findMethodDeclaration(classLoaderCreation); if (method != null) { final Set<String> variableReferences = findVariableReferences(classLoaderCreation.arguments()); if (!variableReferences.isEmpty()) { updateMethodParams(rewrite, variableReferences, method.parameters()); updateLocalVariableDeclarations(rewrite, variableReferences, method.getBody()); } } } protected void updateMethodParams(ASTRewrite rewrite, Set<String> variables, List<?> params) { Assert.isNotNull(rewrite); Assert.isNotNull(variables); Assert.isNotNull(params); for (Object paramObj : params) { SingleVariableDeclaration param = (SingleVariableDeclaration) paramObj; if (variables.contains(param.getName().getFullyQualifiedName())) { ListRewrite listRewrite = rewrite.getListRewrite(param, SingleVariableDeclaration.MODIFIERS2_PROPERTY); listRewrite.insertLast(rewrite.getAST().newModifier(ModifierKeyword.FINAL_KEYWORD), null); } } } protected void updateLocalVariableDeclarations(final ASTRewrite rewrite, final Set<String> variables, Block block) { Assert.isNotNull(rewrite); Assert.isNotNull(block); Assert.isNotNull(variables); final AST ast = rewrite.getAST(); block.accept(new ASTVisitor() { @Override public boolean visit(VariableDeclarationFragment fragment) { if (variables.contains(fragment.getName().getFullyQualifiedName())) { ASTNode parent = fragment.getParent(); if (parent instanceof VariableDeclarationStatement) { ListRewrite listRewrite = rewrite .getListRewrite(parent, VariableDeclarationStatement.MODIFIERS2_PROPERTY); listRewrite.insertLast(ast.newModifier(ModifierKeyword.FINAL_KEYWORD), null); } } return true; } }); } protected void updateImportDeclarations(ASTRewrite rewrite, CompilationUnit compilationUnit) { Assert.isNotNull(rewrite); Assert.isNotNull(compilationUnit); if (isUpdateImports()) { final AST ast = rewrite.getAST(); SortedSet<ImportDeclaration> imports = new TreeSet<ImportDeclaration>(importComparator); imports.add(createImportDeclaration(ast, PrivilegedAction.class)); if (!isStaticImport()) { imports.add(createImportDeclaration(ast, AccessController.class)); } else { imports.add(createImportDeclaration(ast, AccessController.class, DO_PRIVILEGED_METHOD_NAME)); } addImports(rewrite, compilationUnit, imports); } } protected ImportDeclaration createImportDeclaration(AST ast, Class<?> importClass) { Assert.isNotNull(ast); Assert.isNotNull(importClass); return createImportDeclaration(ast, importClass.getName(), false); } protected ImportDeclaration createImportDeclaration(AST ast, Class<?> importClass, String javaElementName) { Assert.isNotNull(ast); Assert.isNotNull(importClass); Assert.isNotNull(javaElementName); return createImportDeclaration(ast, importClass.getName() + "." + javaElementName, true); } private ImportDeclaration createImportDeclaration(AST ast, String name, boolean isStatic) { ImportDeclaration importDeclaration = ast.newImportDeclaration(); importDeclaration.setName(ast.newName(name)); importDeclaration.setStatic(isStatic); return importDeclaration; } protected MethodInvocation createDoPrivilegedInvocation(ASTRewrite rewrite, ClassInstanceCreation classLoaderCreation) { AST ast = rewrite.getAST(); MethodInvocation doPrivilegedInvocation = ast.newMethodInvocation(); ClassInstanceCreation privilegedActionCreation = createPrivilegedActionCreation(rewrite, classLoaderCreation); List<Expression> arguments = checkedList(doPrivilegedInvocation.arguments()); if (!isStaticImport()) { Name accessControllerName; if (isUpdateImports()) { accessControllerName = ast.newSimpleName(AccessController.class.getSimpleName()); } else { accessControllerName = ast.newName(AccessController.class.getName()); } doPrivilegedInvocation.setExpression(accessControllerName); } doPrivilegedInvocation.setName(ast.newSimpleName(DO_PRIVILEGED_METHOD_NAME)); arguments.add(privilegedActionCreation); return doPrivilegedInvocation; } private ClassInstanceCreation createPrivilegedActionCreation(ASTRewrite rewrite, ClassInstanceCreation classLoaderCreation) { AST ast = rewrite.getAST(); ClassInstanceCreation privilegedActionCreation = ast.newClassInstanceCreation(); ParameterizedType privilegedActionType = createPrivilegedActionType(rewrite, classLoaderCreation); AnonymousClassDeclaration anonymousClassDeclaration = createAnonymousClassDeclaration(rewrite, classLoaderCreation); privilegedActionCreation.setType(privilegedActionType); privilegedActionCreation.setAnonymousClassDeclaration(anonymousClassDeclaration); return privilegedActionCreation; } private ParameterizedType createPrivilegedActionType(ASTRewrite rewrite, ClassInstanceCreation classLoaderCreation) { AST ast = rewrite.getAST(); Name privilegedActionName; if (isUpdateImports()) { privilegedActionName = ast.newSimpleName(PrivilegedAction.class.getSimpleName()); } else { privilegedActionName = ast.newName(PrivilegedAction.class.getName()); } SimpleType rawPrivilegedActionType = ast.newSimpleType(privilegedActionName); ParameterizedType privilegedActionType = ast.newParameterizedType(rawPrivilegedActionType); Type typeArgument = (Type) rewrite.createCopyTarget(classLoaderCreation.getType()); List<Type> typeArguments = checkedList(privilegedActionType.typeArguments()); typeArguments.add(typeArgument); return privilegedActionType; } @SuppressWarnings("unchecked") <V> List<V> checkedList(List<?> o) { return (List<V>) o; } private AnonymousClassDeclaration createAnonymousClassDeclaration(ASTRewrite rewrite, ClassInstanceCreation classLoaderCreation) { AST ast = rewrite.getAST(); AnonymousClassDeclaration anonymousClassDeclaration = ast.newAnonymousClassDeclaration(); MethodDeclaration runMethodDeclaration = createRunMethodDeclaration(rewrite, classLoaderCreation); List<BodyDeclaration> bodyDeclarations = checkedList(anonymousClassDeclaration.bodyDeclarations()); bodyDeclarations.add(runMethodDeclaration); return anonymousClassDeclaration; } private MethodDeclaration createRunMethodDeclaration(ASTRewrite rewrite, ClassInstanceCreation classLoaderCreation) { AST ast = rewrite.getAST(); MethodDeclaration methodDeclaration = ast.newMethodDeclaration(); SimpleName methodName = ast.newSimpleName("run"); Type returnType = (Type) rewrite.createCopyTarget(classLoaderCreation.getType()); Block methodBody = createRunMethodBody(rewrite, classLoaderCreation); List<Modifier> modifiers = checkedList(methodDeclaration.modifiers()); modifiers.add(ast.newModifier(PUBLIC_KEYWORD)); methodDeclaration.setName(methodName); methodDeclaration.setReturnType2(returnType); methodDeclaration.setBody(methodBody); return methodDeclaration; } private Block createRunMethodBody(ASTRewrite rewrite, ClassInstanceCreation classLoaderCreation) { AST ast = rewrite.getAST(); Block methodBody = ast.newBlock(); ReturnStatement returnStatement = ast.newReturnStatement(); List<Statement> statements = checkedList(methodBody.statements()); statements.add(returnStatement); returnStatement.setExpression((ClassInstanceCreation) rewrite.createCopyTarget(classLoaderCreation)); return methodBody; } @CheckForNull private ClassInstanceCreation findClassLoaderCreation(ASTNode node) { ClassLoaderCreationFinder finder = new ClassLoaderCreationFinder(); node.accept(finder); return finder.getClassLoaderCreation(); } @CheckForNull private MethodDeclaration findMethodDeclaration(ASTNode node) { if (node == null || node instanceof MethodDeclaration) { return (MethodDeclaration) node; } return findMethodDeclaration(node.getParent()); } private Set<String> findVariableReferences(List<?> arguments) { final Set<String> refs = new HashSet<String>(); for (Object argumentObj : arguments) { Expression argument = (Expression) argumentObj; argument.accept(new ASTVisitor() { @Override public boolean visit(SimpleName node) { if (!(node.getParent() instanceof Type)) { refs.add(node.getFullyQualifiedName()); } return true; } }); } return refs; } protected static class ClassLoaderCreationFinder extends ASTVisitor { private ClassInstanceCreation classLoaderCreation; @Override public boolean visit(ClassInstanceCreation node) { if (classLoaderCreation == null) { if (!isClassLoaderCreation(node)) { return true; } classLoaderCreation = node; } return false; } public ClassInstanceCreation getClassLoaderCreation() { return classLoaderCreation; } private static boolean isClassLoaderCreation(ClassInstanceCreation node) { return isClassLoader(node.getType()); } private static boolean isClassLoader(Type type) { return isClassLoader(type.resolveBinding()); } private static boolean isClassLoader(ITypeBinding typeBinding) { if (typeBinding.getQualifiedName().equals(ClassLoader.class.getName())) { return true; } ITypeBinding superclass = typeBinding.getSuperclass(); return superclass != null && isClassLoader(superclass); } } }